# apps/subjects/tests.py
"""
学科管理应用测试套件
涵盖模型、序列化器、视图集（API）的完整测试
"""
from django.contrib.auth.models import User
from django.urls import reverse
from rest_framework import status
from rest_framework.test import APITestCase
from rest_framework.test import APIClient

from .models import Subject
from ..users.models import QBUser


class SubjectModelTest(APITestCase):
    """测试 Subject 模型的基本行为和方法"""

    def setUp(self):
        self.primary_subject = Subject.objects.create(
            name="数学",
            code="MATH",
            level=1,
            is_active=True
        )
        self.secondary_subject = Subject.objects.create(
            name="代数",
            code="ALGEBRA",
            level=2,
            parent=self.primary_subject,
            is_active=True
        )

    def test_subject_str(self):
        """测试 __str__ 方法"""
        self.assertEqual(str(self.primary_subject), "数学")
        self.assertEqual(str(self.secondary_subject), "代数")

    def test_get_level_display(self):
        """测试 get_level_display 方法"""
        self.assertEqual(self.primary_subject.get_level_display(), "一级学科")
        self.assertEqual(self.secondary_subject.get_level_display(), "二级学科")

    def test_is_primary_and_is_secondary(self):
        """测试 is_primary 和 is_secondary 方法"""
        self.assertTrue(self.primary_subject.is_primary())
        self.assertFalse(self.primary_subject.is_secondary())

        self.assertTrue(self.secondary_subject.is_secondary())
        self.assertFalse(self.secondary_subject.is_primary())

    def test_has_children(self):
        """测试 has_children 方法"""
        self.assertTrue(self.primary_subject.has_children())
        self.assertFalse(self.secondary_subject.has_children())

    def test_cannot_set_self_as_parent(self):
        """测试不能将自己设为父级"""
        with self.assertRaises(Exception):
            self.primary_subject.parent = self.primary_subject
            self.primary_subject.save()

    def test_parent_must_be_level_1(self):
        """测试二级学科的父级必须是一级学科"""
        with self.assertRaises(Exception):
            invalid_subject = Subject(
                name="无效子学科",
                level=2,
                parent=self.secondary_subject  # 二级不能作为父级
            )
            invalid_subject.full_clean()  # 触发验证


class SubjectAPITest(APITestCase):
    """测试 Subject API 视图集的所有端点"""

    def setUp(self):
        self.client = APIClient()
        # 创建测试用户并登录
        self.QBuser = QBUser.objects.create_user(username='testuser', password='testpass')
        self.client.force_authenticate(user=self.QBuser)
        # 删除旧数据（可选）
        Subject.objects.all().delete()
        # 创建测试数据
        self.math = Subject.objects.create(
            name="数学",
            code="MATH",
            level=1,
            sort_order=1,
            is_active=True
        )
        self.chinese = Subject.objects.create(
            name="语文",
            code="CHINESE",
            level=1,
            sort_order=2,
            is_active=True
        )
        self.algebra = Subject.objects.create(
            name="代数",
            code="ALGEBRA",
            level=2,
            parent=self.math,
            sort_order=1,
            is_active=True
        )
        self.geometry = Subject.objects.create(
            name="几何",
            code="GEOMETRY",
            level=2,
            parent=self.math,
            sort_order=2,
            is_active=False
        )

    def test_list_subjects(self):
        """测试获取学科列表（默认仅返回激活的）"""
        url = reverse('subject-list')
        response = self.client.get(url)
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(len(response.data), 3)  # math, chinese, algebra (geometry is inactive)
        names = [item['name'] for item in response.data]
        self.assertIn("数学", names)
        self.assertIn("代数", names)
        self.assertNotIn("几何", names)

    def test_retrieve_subject(self):
        """测试获取单个学科详情"""
        url = reverse('subject-detail', kwargs={'pk': self.math.id})
        response = self.client.get(url)
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(response.data['name'], "数学")
        self.assertEqual(response.data['level_label'], "一级学科")
        self.assertEqual(response.data['has_children'], True)

    def test_create_subject_primary(self):
        """测试创建一级学科"""
        url = reverse('subject-list')
        data = {
            "name": "物理",
            "code": "PHYSICS",
            "level": 1,
            "is_active": True,
            "sort_order": 3
        }
        response = self.client.post(url, data, format='json')
        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
        self.assertEqual(response.data['name'], "物理")
        self.assertEqual(response.data['parent'], None)
        self.assertEqual(Subject.objects.count(), 5)  # 原4个 + 1个

    def test_create_subject_secondary(self):
        """测试创建二级学科"""
        url = reverse('subject-list')
        data = {
            "name": "三角函数",
            "code": "TRIG",
            "level": 2,
            "parent": self.math.id,
            "is_active": True
        }
        response = self.client.post(url, data, format='json')
        self.assertEqual(response.status_code, status.HTTP_201_CREATED)
        self.assertEqual(response.data['parent'], self.math.id)
        self.assertEqual(response.data['parent_name'], "数学")

    def test_create_subject_invalid_parent_for_level1(self):
        """测试一级学科不能有父级"""
        url = reverse('subject-list')
        data = {
            "name": "错误学科",
            "level": 1,
            "parent": self.math.id
        }
        response = self.client.post(url, data, format='json')
        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
        self.assertIn("一级学科不能设置父级学科", str(response.data))

    def test_create_subject_secondary_missing_parent(self):
        """测试二级学科必须有父级"""
        url = reverse('subject-list')
        data = {
            "name": "缺失父级",
            "level": 2
        }
        response = self.client.post(url, data, format='json')
        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
        self.assertIn("二级学科必须选择一个父级学科", str(response.data))

    def test_update_subject(self):
        """测试更新学科"""
        url = reverse('subject-detail', kwargs={'pk': self.algebra.id})
        data = {
            "name": "高等代数",
            "is_active": False
        }
        response = self.client.patch(url, data, format='json')
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.algebra.refresh_from_db()
        self.assertEqual(self.algebra.name, "高等代数")
        self.assertFalse(self.algebra.is_active)

    def test_list_tree(self):
        """测试获取树形结构"""
        url = reverse('subject-list-tree')
        response = self.client.get(url)
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(len(response.data), 2)  # 数学、语文

        math_data = next(item for item in response.data if item['name'] == "数学")
        self.assertEqual(len(math_data['children']), 1)  # 仅代数（几何未激活）
        self.assertEqual(math_data['children'][0]['name'], "代数")

    def test_minimal_list(self):
        """测试极简列表"""
        url = reverse('subject-minimal')
        response = self.client.get(url)
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        for item in response.data:
            self.assertIn('id', item)
            self.assertIn('name', item)
            self.assertIn('code', item)
            self.assertIn('level', item)
            self.assertNotIn('parent_name', item)
            self.assertNotIn('created_at', item)

    def test_activate_subject(self):
        """测试启用学科"""
        url = reverse('subject-activate', kwargs={'pk': self.geometry.id})
        response = self.client.post(url)
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.geometry.refresh_from_db()
        self.assertTrue(self.geometry.is_active)

    def test_deactivate_subject(self):
        """测试禁用学科"""
        url = reverse('subject-deactivate', kwargs={'pk': self.algebra.id})
        response = self.client.post(url)
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.algebra.refresh_from_db()
        self.assertFalse(self.algebra.is_active)

    def test_deactivate_subject_with_active_children_fails(self):
        """测试禁用带有活跃子学科的学科会失败"""
        # 先创建一个活跃的子学科
        Subject.objects.create(
            name="子代数",
            level=3,
            parent=self.algebra,
            is_active=True
        )
        url = reverse('subject-deactivate', kwargs={'pk': self.algebra.id})
        response = self.client.post(url)
        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
        self.assertIn("存在启用的子学科", str(response.data))
        self.algebra.refresh_from_db()
        self.assertTrue(self.algebra.is_active)  # 应未被禁用

    def test_bulk_deactivate(self):
        """测试批量禁用"""
        url = reverse('subject-bulk-deactivate')
        data = {"ids": [self.algebra.id, self.geometry.id]}

        # 调试：打印要禁用的学科
        print("要禁用的学科 ID:", data['ids'])
        for sid in data['ids']:
            subj = Subject.objects.filter(id=sid).first()
            print(f"ID={sid}, Name={subj.name if subj else 'None'}, is_active={subj.is_active if subj else None}")

        response = self.client.post(url, data, format='json')
        print("响应数据:", response.data)

        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(response.data['message'], '成功禁用 2 个学科')

        self.algebra.refresh_from_db()
        self.geometry.refresh_from_db()
        self.assertFalse(self.algebra.is_active)
        self.assertFalse(self.geometry.is_active)

    def test_bulk_deactivate_with_children_fails(self):
        """测试批量禁用时，若任一学科有子学科则失败"""
        # 给代数加一个子学科
        Subject.objects.create(
            name="子代数",
            level=3,
            parent=self.algebra,
            is_active=True
        )
        url = reverse('subject-bulk-deactivate')
        data = {"ids": [self.algebra.id, self.chinese.id]}
        response = self.client.post(url, data, format='json')
        self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
        print("响应数据:", response.data)
        self.assertIn("存在子学科", str(response.data))

    def test_search_subject(self):
        """测试搜索功能"""
        url = reverse('subject-search')
        response = self.client.get(url, {'q': '数'})
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        results = response.data.get('results', [])
        print("搜索结果:", results)
        names = [item['name'] for item in results]
        self.assertIn("数学", names)
        self.assertIn("代数", names)
        self.assertNotIn("语文", names)

        # 测试编码搜索
        response = self.client.get(url, {'q': 'MATH'})
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        results1 = response.data.get('results', [])
        names = [item['name'] for item in results1]
        self.assertIn("数学", names)

    def test_search_empty_query(self):
        """测试空搜索返回空列表"""
        url = reverse('subject-search')
        response = self.client.get(url, {'q': ''})
        self.assertEqual(response.status_code, status.HTTP_200_OK)
        self.assertEqual(response.data, [])